/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS system libraries
 * FILE:            lib/rtl/env.c
 * PURPOSE:         Environment functions
 * PROGRAMMER:      Eric Kohl
 */

/* INCLUDES ******************************************************************/

#include <rtl.h>

#define NDEBUG
#include <debug.h>

/* FUNCTIONS *****************************************************************/

NTSTATUS
NTAPI
RtlSetEnvironmentStrings(IN PWCHAR NewEnvironment, IN ULONG NewEnvironmentSize)
{
    UNIMPLEMENTED;
    return STATUS_NOT_IMPLEMENTED;
}

/*
 * @implemented
 */
NTSTATUS
NTAPI
RtlCreateEnvironment(
    BOOLEAN Inherit,
    PWSTR *OutEnvironment)
{
    MEMORY_BASIC_INFORMATION MemInfo;
    PVOID CurrentEnvironment, NewEnvironment = NULL;
    NTSTATUS Status = STATUS_SUCCESS;
    SIZE_T RegionSize = PAGE_SIZE;

    /* Check if we should inherit the current environment */
    if (Inherit)
    {
        /* In this case we need to lock the PEB */
        RtlAcquirePebLock();

        /* Get a pointer to the current Environment and check if it's not NULL */
        CurrentEnvironment = NtCurrentPeb()->ProcessParameters->Environment;
        if (CurrentEnvironment != NULL)
        {
            /* Query the size of the current environment allocation */
            Status = NtQueryVirtualMemory(NtCurrentProcess(),
                                          CurrentEnvironment,
                                          MemoryBasicInformation,
                                          &MemInfo,
                                          sizeof(MEMORY_BASIC_INFORMATION),
                                          NULL);
            if (!NT_SUCCESS(Status))
            {
                RtlReleasePebLock();
                *OutEnvironment = NULL;
                return Status;
            }

            /* Allocate a new region of the same size */
            RegionSize = MemInfo.RegionSize;
            Status = NtAllocateVirtualMemory(NtCurrentProcess(),
                                             &NewEnvironment,
                                             0,
                                             &RegionSize,
                                             MEM_RESERVE | MEM_COMMIT,
                                             PAGE_READWRITE);
            if (!NT_SUCCESS(Status))
            {
                RtlReleasePebLock();
                *OutEnvironment = NULL;
                return Status;
            }

            /* Copy the current environment */
            RtlCopyMemory(NewEnvironment,
                          CurrentEnvironment,
                          MemInfo.RegionSize);
        }

        /* We are done with the PEB, release the lock */
        RtlReleasePebLock ();
    }

    /* Check if we still need an environment */
    if (NewEnvironment == NULL)
    {
        /* Allocate a new environment */
        Status = NtAllocateVirtualMemory(NtCurrentProcess(),
                                         &NewEnvironment,
                                         0,
                                         &RegionSize,
                                         MEM_RESERVE | MEM_COMMIT,
                                         PAGE_READWRITE);
        if (NT_SUCCESS(Status))
        {
            RtlZeroMemory(NewEnvironment, RegionSize);
        }
    }

    *OutEnvironment = NewEnvironment;

    return Status;
}


/*
 * @implemented
 */
VOID NTAPI
RtlDestroyEnvironment(PWSTR Environment)
{
   SIZE_T Size = 0;

   NtFreeVirtualMemory(NtCurrentProcess(),
                       (PVOID)&Environment,
                       &Size,
                       MEM_RELEASE);
}


/*
 * @implemented
 */
NTSTATUS NTAPI
RtlExpandEnvironmentStrings_U(PWSTR Environment,
                              PUNICODE_STRING Source,
                              PUNICODE_STRING Destination,
                              PULONG Length)
{
   UNICODE_STRING Variable;
   UNICODE_STRING Value;
   NTSTATUS ReturnStatus = STATUS_SUCCESS;
   NTSTATUS Status;
   PWSTR SourceBuffer;
   PWSTR DestBuffer;
   PWSTR CopyBuffer;
   PWSTR VariableEnd;
   ULONG SourceLength;
   ULONG DestMax;
   ULONG CopyLength;
   ULONG Tail;
   ULONG TotalLength = 1; /* for terminating NULL */

   DPRINT("RtlExpandEnvironmentStrings_U %p %wZ %p %p\n",
          Environment, Source, Destination, Length);

   SourceLength = Source->Length / sizeof(WCHAR);
   SourceBuffer = Source->Buffer;
   DestMax = Destination->MaximumLength / sizeof(WCHAR);
   DestBuffer = Destination->Buffer;

   while (SourceLength)
   {
      if (*SourceBuffer != L'%')
      {
         CopyBuffer = SourceBuffer;
         CopyLength = 0;
         while (SourceLength != 0 && *SourceBuffer != L'%')
         {
            SourceBuffer++;
            CopyLength++;
            SourceLength--;
         }
      }
      else
      {
         /* Process environment variable. */

         VariableEnd = SourceBuffer + 1;
         Tail = SourceLength - 1;
         while (*VariableEnd != L'%' && Tail != 0)
         {
            VariableEnd++;
            Tail--;
         }

         if (Tail != 0)
         {
            Variable.MaximumLength =
            Variable.Length = (USHORT)(VariableEnd - (SourceBuffer + 1)) * sizeof(WCHAR);
            Variable.Buffer = SourceBuffer + 1;

            Value.Length = 0;
            Value.MaximumLength = (USHORT)DestMax * sizeof(WCHAR);
            Value.Buffer = DestBuffer;

            Status = RtlQueryEnvironmentVariable_U(Environment, &Variable,
                                                   &Value);
            if (NT_SUCCESS(Status) || Status == STATUS_BUFFER_TOO_SMALL)
            {
                SourceBuffer = VariableEnd + 1;
                SourceLength = Tail - 1;
                TotalLength += Value.Length / sizeof(WCHAR);
                if (Status != STATUS_BUFFER_TOO_SMALL)
                {
                   DestBuffer += Value.Length / sizeof(WCHAR);
                   DestMax -= Value.Length / sizeof(WCHAR);
                }
                else
                {
                   DestMax = 0;
                   ReturnStatus = STATUS_BUFFER_TOO_SMALL;
                }
                continue;
            }
            else
            {
               /* Variable not found. */
               CopyBuffer = SourceBuffer;
               CopyLength = SourceLength - Tail + 1;
               SourceLength -= CopyLength;
               SourceBuffer += CopyLength;
            }
         }
         else
         {
            /* Unfinished variable name. */
            CopyBuffer = SourceBuffer;
            CopyLength = SourceLength;
            SourceLength = 0;
         }
      }

      TotalLength += CopyLength;
      if (DestMax)
      {
         if (DestMax < CopyLength)
         {
            CopyLength = DestMax;
            ReturnStatus = STATUS_BUFFER_TOO_SMALL;
         }
         RtlCopyMemory(DestBuffer, CopyBuffer, CopyLength * sizeof(WCHAR));
         DestMax -= CopyLength;
         DestBuffer += CopyLength;
      }
   }

   /* NULL-terminate the buffer. */
   if (DestMax)
      *DestBuffer = 0;
   else
      ReturnStatus = STATUS_BUFFER_TOO_SMALL;

   Destination->Length = (USHORT)(DestBuffer - Destination->Buffer) * sizeof(WCHAR);
   if (Length != NULL)
      *Length = TotalLength * sizeof(WCHAR);

   DPRINT("Destination %wZ\n", Destination);

   return ReturnStatus;
}


/*
 * @implemented
 */
VOID NTAPI
RtlSetCurrentEnvironment(PWSTR NewEnvironment,
                         PWSTR *OldEnvironment)
{
   PVOID EnvPtr;

   DPRINT("NewEnvironment 0x%p OldEnvironment 0x%p\n",
          NewEnvironment, OldEnvironment);

   RtlAcquirePebLock();

   EnvPtr = NtCurrentPeb()->ProcessParameters->Environment;
   NtCurrentPeb()->ProcessParameters->Environment = NewEnvironment;

   if (OldEnvironment != NULL)
      *OldEnvironment = EnvPtr;

   RtlReleasePebLock();
}


/*
 * @implemented
 */
NTSTATUS NTAPI
RtlSetEnvironmentVariable(PWSTR *Environment,
                          PUNICODE_STRING Name,
                          PUNICODE_STRING Value)
{
   MEMORY_BASIC_INFORMATION mbi;
   UNICODE_STRING var;
   size_t hole_len, new_len, env_len = 0;
   WCHAR *new_env = 0, *env_end = 0, *wcs, *env, *val = 0, *tail = 0, *hole = 0;
   PWSTR head = NULL;
   SIZE_T size = 0, new_size;
   LONG f = 1;
   NTSTATUS Status = STATUS_SUCCESS;

   DPRINT("RtlSetEnvironmentVariable(Environment %p Name %wZ Value %wZ)\n",
          Environment, Name, Value);

   /* Variable name must not be empty */
   if (Name->Length < sizeof(WCHAR))
      return STATUS_INVALID_PARAMETER;

   /* Variable names can't contain a '=' except as a first character. */
   for (wcs = Name->Buffer + 1;
        wcs < Name->Buffer + (Name->Length / sizeof(WCHAR));
        wcs++)
   {
      if (*wcs == L'=')
          return STATUS_INVALID_PARAMETER;
   }

   if (Environment)
   {
      env = *Environment;
   }
   else
   {
      RtlAcquirePebLock();
      env = NtCurrentPeb()->ProcessParameters->Environment;
   }

   if (env)
   {
      /* get environment length */
      wcs = env_end = env;
      do
      {
         env_end += wcslen(env_end) + 1;
      }
      while (*env_end);
      env_end++;
      env_len = env_end - env;
      DPRINT("environment length %lu characters\n", env_len);

      /* find where to insert */
      while (*wcs)
      {
         var.Buffer = wcs++;
         wcs = wcschr(wcs, L'=');
         if (wcs == NULL)
         {
            wcs = var.Buffer + wcslen(var.Buffer);
         }
         if (*wcs)
         {
            var.Length = (USHORT)(wcs - var.Buffer) * sizeof(WCHAR);
            var.MaximumLength = var.Length;
            val = ++wcs;
            wcs += wcslen(wcs);
            f = RtlCompareUnicodeString(&var, Name, TRUE);
            if (f >= 0)
            {
               if (f) /* Insert before found */
               {
                  hole = tail = var.Buffer;
               }
               else /* Exact match */
               {
                  head = var.Buffer;
                  tail = ++wcs;
                  hole = val;
               }
               goto found;
            }
         }
         wcs++;
      }
      hole = tail = wcs; /* Append to environment */
   }

found:
   if (Value != NULL && Value->Buffer != NULL)
   {
      hole_len = tail - hole;
      /* calculate new environment size */
      new_size = Value->Length + sizeof(WCHAR);
      /* adding new variable */
      if (f)
         new_size += Name->Length + sizeof(WCHAR);
      new_len = new_size / sizeof(WCHAR);
      if (hole_len < new_len)
      {
         /* enlarge environment size */
         /* check the size of available memory */
         new_size += (env_len - hole_len) * sizeof(WCHAR);
         new_size = ROUND_UP(new_size, PAGE_SIZE);
         mbi.RegionSize = 0;
         DPRINT("new_size %lu\n", new_size);

         if (env)
         {
            Status = NtQueryVirtualMemory(NtCurrentProcess(),
                                          env,
                                          MemoryBasicInformation,
                                          &mbi,
                                          sizeof(MEMORY_BASIC_INFORMATION),
                                          NULL);
            if (!NT_SUCCESS(Status))
            {
               if (Environment == NULL)
               {
                  RtlReleasePebLock();
               }
               return(Status);
            }
         }

         if (new_size > mbi.RegionSize)
         {
            /* reallocate memory area */
            Status = NtAllocateVirtualMemory(NtCurrentProcess(),
                                             (PVOID)&new_env,
                                             0,
                                             &new_size,
                                             MEM_RESERVE | MEM_COMMIT,
                                             PAGE_READWRITE);
            if (!NT_SUCCESS(Status))
            {
               if (Environment == NULL)
               {
                  RtlReleasePebLock();
               }
               return(Status);
            }

            if (env)
            {
               memmove(new_env,
                       env,
                       (hole - env) * sizeof(WCHAR));
               hole = new_env + (hole - env);
            }
            else
            {
               /* absolutely new environment */
               tail = hole = new_env;
               *hole = 0;
               env_end = hole + 1;
            }
         }
      }

      /* move tail */
      memmove (hole + new_len, tail, (env_end - tail) * sizeof(WCHAR));

      if (new_env)
      {
         /* we reallocated environment, let's free the old one */
         if (Environment)
            *Environment = new_env;
         else
            NtCurrentPeb()->ProcessParameters->Environment = new_env;

         if (env)
         {
            size = 0;
            NtFreeVirtualMemory(NtCurrentProcess(),
                                (PVOID)&env,
                                &size,
                                MEM_RELEASE);
         }
      }

      /* and now copy given stuff */
      if (f)
      {
         /* copy variable name and '=' character */
         memmove(hole,
                 Name->Buffer,
                 Name->Length);
         hole += Name->Length / sizeof(WCHAR);
         *hole++ = L'=';
      }

      /* copy value */
      memmove(hole,
              Value->Buffer,
              Value->Length);
      hole += Value->Length / sizeof(WCHAR);
      *hole = 0;
   }
   else
   {
      /* remove the environment variable */
      if (f == 0)
      {
         memmove(head,
                 tail,
                 (env_end - tail) * sizeof(WCHAR));
      }
   }

   if (Environment == NULL)
   {
      RtlReleasePebLock();
   }

   return(Status);
}


/*
 * @implemented
 */
NTSTATUS NTAPI
RtlQueryEnvironmentVariable_U(PWSTR Environment,
                              PCUNICODE_STRING Name,
                              PUNICODE_STRING Value)
{
   NTSTATUS Status;
   PWSTR wcs;
   UNICODE_STRING var;
   PWSTR val;
   BOOLEAN SysEnvUsed = FALSE;

   DPRINT("RtlQueryEnvironmentVariable_U Environment %p Variable %wZ Value %p\n",
          Environment, Name, Value);

   if (Environment == NULL)
   {
      PPEB Peb = RtlGetCurrentPeb();
      if (Peb) {
          RtlAcquirePebLock();
          Environment = Peb->ProcessParameters->Environment;
          SysEnvUsed = TRUE;
      }
   }

   if (Environment == NULL)
   {
      if (SysEnvUsed)
         RtlReleasePebLock();
      return(STATUS_VARIABLE_NOT_FOUND);
   }

   Value->Length = 0;

   wcs = Environment;
   DPRINT("Starting search at :%p\n", wcs);
   while (*wcs)
   {
      var.Buffer = wcs++;
      wcs = wcschr(wcs, L'=');
      if (wcs == NULL)
      {
         wcs = var.Buffer + wcslen(var.Buffer);
         DPRINT("Search at :%S\n", wcs);
      }
      if (*wcs)
      {
         var.Length = var.MaximumLength = (USHORT)(wcs - var.Buffer) * sizeof(WCHAR);
         val = ++wcs;
         wcs += wcslen(wcs);
         DPRINT("Search at :%S\n", wcs);

         if (RtlEqualUnicodeString(&var, Name, TRUE))
         {
            Value->Length = (USHORT)(wcs - val) * sizeof(WCHAR);
            if (Value->Length <= Value->MaximumLength)
            {
               memcpy(Value->Buffer, val,
                      min(Value->Length + sizeof(WCHAR), Value->MaximumLength));
               DPRINT("Value %S\n", val);
               DPRINT("Return STATUS_SUCCESS\n");
               Status = STATUS_SUCCESS;
            }
            else
            {
               DPRINT("Return STATUS_BUFFER_TOO_SMALL\n");
               Status = STATUS_BUFFER_TOO_SMALL;
            }

            if (SysEnvUsed)
               RtlReleasePebLock();

            return(Status);
         }
      }
      wcs++;
   }

   if (SysEnvUsed)
      RtlReleasePebLock();

   DPRINT("Return STATUS_VARIABLE_NOT_FOUND: %wZ\n", Name);
   return(STATUS_VARIABLE_NOT_FOUND);
}

/* EOF */
